<template>
  <view :style="{ height: getHeight + 'rpx' }" class="tui-drag__wrap">
    <view v-for="(item, index) in list"
          :key="item.id"
          :class="{
            'tui-drag__current': current === index,
            'tui-drag__transition': current !== index && !isInit,
            'tui-drag__fixed': item.fixed,
            'tui-drag__hidden': isInit
          }"
          :data-index="index"
          :style="{
            width: 100 / columns + '%',
            height: itemHeight + 'rpx',
            transform: `translate3d(${index === current && !item.fixed ? transX : item.transX}, ${index === current && !item.fixed ? transY : item.transY}, 0px)`
          }"
          class="tui-drag__item"
          @touchend="touchend"
          @touchstart="touchstart"
          @touchmove.stop.prevent="touchmove">
      <slot :entity="item.data" :fixed="item.fixed" :height="itemHeight" :index="index" :isEdit="isEdit">
      </slot>
    </view>
  </view>
</template>

<script>
import mpdrag from './tui-drag.js'

export default {
  name: 'tuiDrag',
  mixins: [mpdrag],
  emits: ['click', 'sortend', 'change'],
  props: {
    listData: {
      type: Array,
      default() {
        return [];
      }
    },
    columns: {
      type: Number,
      default: 3
    },
    topSize: {
      type: Number,
      default: 0
    },
    bottomSize: {
      type: Number,
      default: 0
    },
    itemHeight: {
      type: Number,
      default: 0
    },
    scrollTop: {
      type: Number,
      default: 0
    },
    isEdit: {
      type: Boolean,
      default: true
    }
  },
  data() {
    return {
      baseData: {},
      platform: '',
      listWxs: [],
      rows: 4,
      list: [],
      dragging: true,
      isInit: true,
      wxDrag: true,
      canMove: false,
      current: -1,
      transX: '0px',
      transY: '0px'
    };
  },
  watch: {
    listData(val) {
      this.init();
    },
    columns(val) {
      this.init();
    }
  },
  computed: {
    getHeight() {
      return this.rows * this.itemHeight;
    }
  },
  mounted() {
    this.$nextTick(() => {
      this.init();
    });
  },
  methods: {
    vibrate() {
      if (this.platform !== 'devtools') uni.vibrateShort();
    },
    initDom(callback) {
      let {windowWidth, windowHeight, platform} = uni.getSystemInfoSync();
      let remScale = (windowWidth || 375) / 375;

      this.platform = platform;

      let baseData = {};
      baseData.windowHeight = windowHeight;
      baseData.realTopSize = (this.topSize * remScale) / 2;
      baseData.realBottomSize = (this.bottomSize * remScale) / 2;
      baseData.columns = this.columns;
      baseData.rows = this.rows;

      const query = uni.createSelectorQuery().in(this);
      query.select('.tui-drag__item').boundingClientRect();
      query.select('.tui-drag__wrap').boundingClientRect();
      query.exec(res => {
        if (res && res.length > 0 && res[0] && res[0].width) {
          baseData.itemWidth = res[0].width;
          baseData.itemHeight = res[0].height;
          baseData.wrapLeft = res[1].left;
          baseData.wrapTop = res[1].top + this.scrollTop;
          this.dragging = false;
          this.baseData = baseData;
          callback && callback()
        }
      });
    },
    init() {
      this.wxDrag = true
      this.isInit = true
      this.dragging = false
      this.canMove = false
      let delItem = item => {
        let obj = {...item}
        let fixed = obj.fixed || false;
        delete obj["fixed"];
        return {
          id: this.unique(),
          fixed: fixed,
          data: {...obj}
        };
      };
      let listData = this.listData,
          _list = [];
      listData.forEach((item) => {
        _list.push(delItem(item));
      });

      let i = 0,
          columns = this.columns;
      let list = (_list || []).map((item, index) => {
        item.realKey = i++;
        item.sortKey = index;

        item.transX = `${(item.sortKey % columns) * this.baseData.itemWidth}px`;
        item.transY = `${Math.floor(item.sortKey / columns) * this.baseData.itemHeight}px`;
        return item;
      });
      this.rows = Math.ceil(list.length / columns);
      this.list = list;
      this.listWxs = list;
      this.$nextTick(() => {
        this.initDom(() => {
          this.list = list.map((item, index) => {
            item.transX = `${(index % columns) * this.baseData.itemWidth}px`;
            item.transY = `${Math.floor(index / columns) * this.baseData.itemHeight}px`;
            return item;
          });
          this.listWxs = this.list;
          this.isInit = false
          this.dragging = true;
          this.canMove = true
        });
      });
    },
    sortCore(sKey, eKey) {
      let endRealKey = -1;
      this.list.forEach((item) => {
        if (item.sortKey === eKey) endRealKey = item.realKey;
      });

      return this.list.map((item) => {
        let cKey = item.sortKey;
        let rKey = item.realKey;
        if (sKey < eKey) {
          if (cKey > sKey && cKey <= eKey) {
            --rKey;
            cKey = this.excludeFix(--cKey, true);
          } else if (cKey === sKey) {
            rKey = endRealKey;
            cKey = eKey;
          }
        } else if (sKey > eKey) {
          if (cKey >= eKey && cKey < sKey) {
            ++rKey;
            cKey = this.excludeFix(++cKey, false);
          } else if (cKey === sKey) {
            rKey = endRealKey;
            cKey = eKey;
          }
        }
        if (item.sortKey !== cKey && !item.fixed) {
          let columns = Number(this.columns)
          item.transX = (cKey % columns) * this.baseData.itemWidth + "px";
          item.transY = Math.floor(cKey / columns) * this.baseData.itemHeight + "px";
          item.sortKey = cKey;
          item.realKey = rKey;
        }
        return item;
      });
    },
    excludeFix(cKey, type) {
      if (this.list[cKey].fixed) {
        var _cKey = type ? --cKey : ++cKey;
        return this.excludeFix(cKey, type);
      }
      return cKey;
    },
    emitsEvent(list, type) {
      let changeList = [],
          itemList = [];

      list.forEach((item) => {
        changeList[item.sortKey] = item;
      });

      changeList.forEach((item) => {
        itemList.push(item.data);
      });
      if (type == "change") {
        this.$emit('change', {listData: itemList});
      } else {
        this.$emit('sortend', {listData: itemList});
      }
    },
    unique(n = 6) {
      let rnd = '';
      for (let i = 0; i < n; i++) rnd += Math.floor(Math.random() * 10);
      return 'tui_' + new Date().getTime() + rnd;
    },
    touchstart(e) {
      if (!this.isEdit) return;
      const touch = e.changedTouches[0] || e.touches[0];
      const index = e.currentTarget.dataset.index;
      this.current = index;
      this.startTouch = touch;
      this.tDragging = true;
      this.sId = touch.identifier;
      this.vibrate();
    },
    touchmove(e) {
      if (!this.isEdit || !this.tDragging) return;
      let touch = e.changedTouches[0] || e.touches[0];
      if (!touch || this.sId !== touch.identifier) return;

      // 预先处理计算过渡效果
      const index = this.current;
      const item = this.list[index];
      let transX = this.columns == 1 ? 0 : touch.clientX - (this.baseData.itemWidth / 2 + this.baseData.wrapLeft);
      let transY = touch.clientY - (this.baseData.itemHeight / 2 + this.baseData.wrapTop);

      if (touch.clientY > this.baseData.windowHeight - this.baseData.itemHeight) {
        this.pageScroll({scrollTop: touch.pageY + this.baseData.itemHeight - this.baseData.windowHeight});
      } else if (touch.clientY < this.baseData.itemHeight) {
        this.pageScroll({scrollTop: touch.pageY - this.baseData.itemHeight});
      }

      item.transX = transX + 'px';
      item.transY = transY + 'px';

      this.transX = transX + 'px';
      this.transY = transY + 'px';

      let startKey = this.list[this.current].sortKey;
      let curX = Math.round(transX / this.baseData.itemWidth);
      let curY = Math.round(transY / this.baseData.itemHeight);
      let endKey = curX + Number(this.columns) * curY;

      var targetItem = this.list[endKey];
      if (targetItem && targetItem.fixed) return;
      if (this.isOutRange(curX, Number(this.columns), curY, this.rows, endKey, this.list.length)) return;
      if (startKey === endKey || startKey === this.preStartKey) return;

      // 更新位置
      this.preStartKey = startKey;

      // 确保过渡效果
      let list = this.sortCore(startKey, endKey);
      this.list.forEach((item, index) => {
        if (startKey <= endKey) {
          if (index >= startKey && index < endKey) {
            this.list[index + 1].transX = this.list[index + 1].transX || `${(index % this.columns) * this.baseData.itemWidth}px`;
            this.list[index + 1].transY = this.list[index + 1].transY || `${Math.floor(index / this.columns) * this.baseData.itemHeight}px`;
          }
        } else {
          if (index <= startKey && index > endKey) {
            this.list[index - 1].transX = this.list[index - 1].transX || `${(index % this.columns) * this.baseData.itemWidth}px`;
            this.list[index - 1].transY = this.list[index - 1].transY || `${Math.floor(index / this.columns) * this.baseData.itemHeight}px`;
          }
        }
      });

      this.emitsEvent(list, "change");
    },
    touchend(e) {
      if (!this.isEdit || !this.tDragging) return;
      this.emitsEvent(this.list, "sortend");
      // Reset states
      this.preStartKey = -1;
      this.tDragging = false;
      this.current = -1;
      this.transX = '0px';
      this.transY = '0px';
    },
    isOutRange(x1, y1, x2, y2, x3, y3) {
      return x1 < 0 || x1 >= y1 || x2 < 0 || x2 >= y2 || x3 < 0 || x3 >= y3;
    },
  }
};
</script>

<style scoped>
.tui-drag__wrap {
  position: relative;
}

.tui-drag__wrap .tui-drag__item {
  position: absolute;
  top: 0;
  left: 0;
  /* 确保平滑过渡 */
  transition: transform 0.0s;
  transition: transform 0.0s ease-out;
}

.tui-drag__wrap .tui-drag__current {
  z-index: 10 !important;
}

.tui-drag__wrap .tui-drag__fixed {
  z-index: 1 !important;
}

.tui-drag__hidden {
  opacity: 0;
  visibility: hidden;
}
</style>
